/*
 * Copyright 2004 original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.extremecomponents.table.view;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.PageContext;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.extremecomponents.table.bean.Column;
import org.extremecomponents.table.bean.Export;
import org.extremecomponents.table.core.PreferencesConstants;
import org.extremecomponents.table.core.TableConstants;
import org.extremecomponents.table.core.TableModel;
import org.extremecomponents.table.view.View;

import org.extremecomponents.table.view.freemarker.FreemarkerManager;
import org.extremecomponents.table.view.freemarker.extend.MessageExtend;
import org.extremecomponents.table.view.freemarker.extend.MethodUtils;
import org.extremecomponents.table.view.freemarker.model.TemplateColumnModel;
import org.extremecomponents.table.view.freemarker.model.TemplateExportModel;
import org.extremecomponents.table.view.freemarker.model.TemplateLimitModel;
import org.extremecomponents.table.view.freemarker.model.TemplateRowModel;
import org.extremecomponents.table.view.freemarker.model.TemplateTableModel;

import freemarker.ext.beans.BeanModel;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.servlet.HttpRequestHashModel;
import freemarker.ext.servlet.HttpSessionHashModel;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.SimpleSequence;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * The new template view use freemarker
 * 
 * usage:
 * 
 * <ul>
 * <li>
 * 	[1] : change table view to "freemarker"
 * </li>
 * <li>
 * 	[2] : config the freemarker template location in extremetable.properties:
 *  	  table.view.freemarker.template=extremetable.ftl
 * </li> 
 * <li>
 * 	[3] : not support cal
 * </li>
 * </ul>
 * 
 * @author guanhw
 */
public class FreeMarkerView implements View {

    private static final Log logger = LogFactory.getLog(FreeMarkerView.class);
    
	private final static String defaultTemplate = "extremetable.ftl";
	private static boolean contextInTemplate = false;
	
	private String viewName = "freemarker";	
	private String defaultManger = "org.extremecomponents.table.view.freemarker.DefaultFreemarkerManager";
	
	private String TEMPLATEPATH = "template";
	private String FREEMARKERMANAGER = "manager";
	private String SETCONTEXTTEMPLATE = "setContext";
	
	private static final String THEME_PATH = "[theme]";
	private static final String CONTEXT_PATH = "[context]";
	
    public static final String KEY_REQUEST_MODEL = "Request";
    public static final String KEY_SESSION_MODEL = "Session";
    
    private String theme = "default";
	
	private Configuration config = null;
	private SimpleHash root = new SimpleHash();
	private SimpleHash constants = new SimpleHash();
	private List rows = new ArrayList();
	private TemplateRowModel row = null;
	
	private FreemarkerManager freemarkerManager;
	
	public void beforeBody(TableModel model) {
		initConstants(model);
		initConfig(model);
		initRoot(model);
		MethodUtils.initMethods(model,root);
	}

	public void body(TableModel model, Column column) {
        if (column.isFirstColumn()) {
            row = new TemplateRowModel(model,model.getRowHandler().getRow());
        }

        row.addColumn(column);

        if (column.isLastColumn()) {
        	rows.add(row);
        }
	}

	public Object afterBody(TableModel model) {
		root.put("rows", rows);
		root.put("message",new BeanModel(new MessageExtend(model),new BeansWrapper()));
		
		/**
		 *  For chart display. maybe you can give a hook there for third part chart get tableModel.
		 * 
		Object chartsModel = model.getTableHandler().getTable().getAttribute(ChartsTag.CHART_KEY);

		if(chartsModel != null && List.class.isAssignableFrom(chartsModel.getClass())){
			List charts = (List)chartsModel;
			if(charts.size() > 0)
				root.put("charts", charts);
		}
		*/
		return doFinally(model);
	}

	/**
	 * init freemarker config, the config cache in freemarkerManager. let user impl freemarkerManager.
	 * @param model
	 */
	protected void initConfig(TableModel model) {
		ServletContext servletContext = null;
		Object context = model.getContext().getContextObject();
		if(PageContext.class.isAssignableFrom(context.getClass()))
		{			
			try {
				servletContext = ((PageContext)context).getServletContext();
				String contextPath = ((HttpServletRequest)((PageContext)context).getRequest()).getContextPath();

				//this.theme = ThemeManager.getTheme();
				this.viewName = model.getTableHandler().getTable().getView();
				
				//init config name add by current view name
				this.TEMPLATEPATH = PreferencesConstants.TABLE_VIEW + viewName + "." + TEMPLATEPATH;
				this.FREEMARKERMANAGER = PreferencesConstants.TABLE_VIEW + viewName + "." + FREEMARKERMANAGER;
				this.SETCONTEXTTEMPLATE = PreferencesConstants.TABLE_VIEW + viewName + "." + SETCONTEXTTEMPLATE;

				String managerClass = model.getPreferences().getPreference(FREEMARKERMANAGER);
				if (!StringUtils.isNotBlank(managerClass)) {
					managerClass = defaultManger;
				}
				
				if(freemarkerManager == null){		
					//TODO need cache		
					//I want use TableCache, but cann't, it's private
					//freemarkerManager = (FreemarkerManager)TableCache.getInstance().getCachedObject(managerClass);
					try {
			            Class classForName = Class.forName(managerClass);
			            if(FreemarkerManager.class.isAssignableFrom(classForName)){
			            	freemarkerManager = (FreemarkerManager)classForName.newInstance();
			            }
			        } catch (Exception e) {
			            String msg = "Could not create the object [" + managerClass + "]. The class was not found or does not exist.";
			            logger.error(msg, e);
			            throw new RuntimeException(msg);
			        }
				}
				
				config = freemarkerManager.getConfiguration(servletContext, model);
				
				/**
				 * theme hook, give a theme path for get the template
				 */
				
				/**
				 * convert image path
				 */
				String imgPath = StringUtils.replace(model.getTableHandler().getTable().getImagePath(),THEME_PATH,theme);
				imgPath = StringUtils.replace(imgPath, CONTEXT_PATH, contextPath);
				
				model.getTableHandler().getTable().setImagePath(imgPath);
				
			} catch (TemplateException e) {
				logger.error(e);
				throw new RuntimeException(e);
			}
		}else{
			logger.error("This is not a WebApplication, Couldn't get ServletContext");
		}
	}

	/**
	 * put some Constants in view. maybe not use
	 * @param model
	 */
	protected void initConstants(TableModel model) {		
		constants.put("TABLE_FILTER_ACTION", model.getTableHandler().prefixWithTableId() + TableConstants.FILTER + TableConstants.ACTION);
		constants.put("TABLE_PAGE", model.getTableHandler().prefixWithTableId() + TableConstants.PAGE);
		constants.put("TABLE_SORT", model.getTableHandler().prefixWithTableId() + TableConstants.SORT);
		constants.put("TABLE_ROWS_DISPLAYED", model.getTableHandler().prefixWithTableId() + TableConstants.ROWS_DISPLAYED);
		constants.put("FILTER_ACTION", TableConstants.FILTER_ACTION);	
		constants.put("EXPORT_TABLE_ID" , org.extremecomponents.table.core.TableConstants.EXPORT_TABLE_ID );
		constants.put("APP_CONTXTPATH" , model.getContext ( ).getContextPath ( ) );
		root.put("constants", constants);
	}
	
	
	/**
	 * init model in view
	 * @param model
	 */
	protected void initRoot(TableModel model) {
		String boolIsContextInTemplate = model.getPreferences().getPreference(SETCONTEXTTEMPLATE);
		if("true".equalsIgnoreCase(boolIsContextInTemplate))
			contextInTemplate = true;
		
		if(contextInTemplate)
		{
			Object context = model.getContext().getContextObject();
			if(PageContext.class.isAssignableFrom(context.getClass()))
			{
				PageContext pc = (PageContext)context;
				ServletContext servletContext = pc.getServletContext();
				HttpServletRequest request = (HttpServletRequest)pc.getRequest();
				HttpServletResponse response = (HttpServletResponse)pc.getResponse();
				buildContextHashModel(servletContext,request,response,config.getObjectWrapper());
			}
		}
		root.put("row", model.getRowHandler().getRow());
		
		root.put("table",new TemplateTableModel(model));
		root.put("limit", new TemplateLimitModel(model));
				
		addColumns("columns", model.getColumnHandler().getColumns(), model);
		
		List exports = new ArrayList();
		List inExports = model.getExportHandler().getExports();
		for(int i=0;i<inExports.size();i++)
		{
			exports.add(new TemplateExportModel(model,(Export)inExports.get(i)));
		}
		root.put("exports", new SimpleSequence(exports));
	}
	
	protected void addColumns(String name, List inColumns, TableModel model)
	{
		List columns = new ArrayList();
		for(int i=0;i<inColumns.size();i++){
			Column column = (Column)inColumns.get(i);
			columns.add(new TemplateColumnModel(model,column));
		}
		root.put(name, new SimpleSequence(columns));
		
	}
	
    private void buildContextHashModel(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ObjectWrapper wrapper) {
   	
        HttpSession session = request.getSession(false);
        if (session != null) {
            root.put(KEY_SESSION_MODEL, new HttpSessionHashModel(session, wrapper));
        } else {
        }

        // Create hash model wrapper for the request
        HttpRequestHashModel requestModel = null;

        if ((requestModel == null) || (requestModel.getRequest() != request)) {
            requestModel = new HttpRequestHashModel(request, response, wrapper);
        }

        root.put(KEY_REQUEST_MODEL, requestModel);
    }
    
    /**
     * prepare eveything, then let's process it 
     * @param model
     * @return
     */
    protected Object doFinally(TableModel model) {		
		String templateFile = getTemplate(model);
		try {
			Template template = config.getTemplate(templateFile);
			
			Writer writer = new StringWriter();			
			template.process(root, writer);
			return writer.toString();
			
		} catch (IOException e) {			
			return e.getMessage();	
		} catch (TemplateException e) {			
			return e.getMessage();	
		}
	}

	/**
	 * usage:paths
	 * 
	 * 1.PreferencesConstants.TABLE_VIEW + viewName + ".template" value;
	 * 2.defaultTemplate;
	 * 3.imgPath + defaultTemplate;	 * 
	 * 
	 * @param model
	 * @return
	 */
	protected String getTemplate(TableModel model) {
		String templateFile = model.getPreferences().getPreference(TEMPLATEPATH);
		if (!StringUtils.isNotBlank(templateFile)) {
			String imgPath = model.getTableHandler().getTable().getImagePath();
			if (StringUtils.isNotBlank(imgPath)) {
	            int index = imgPath.indexOf("*.");
	            imgPath = imgPath.substring(0, index);
	            String contextPath = ((HttpServletRequest)((PageContext)model.getContext().getContextObject()).getRequest()).getContextPath();
	            
	            if(imgPath.startsWith(contextPath)){
	            	imgPath = imgPath.substring(contextPath.length());
	            	if(imgPath.startsWith("/"))
	            		imgPath = imgPath.substring(1);
	            }
	            templateFile = imgPath + defaultTemplate;
	        }else{
	        	templateFile = defaultTemplate;
	        }
        }else{
        	templateFile = StringUtils.replace(templateFile, "[theme]", theme);
        }
		return templateFile;
	}
}
